package com.dayatang.hrm.compensation.domain;

import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;

import com.dayatang.commons.domain.AbstractEntity;
import com.dayatang.commons.domain.InstanceFactory;
import com.dayatang.hrm.compensation.context.CompensationMessageSource;
import com.dayatang.hrm.compensation.domain.exception.CompensationRuntimeException;
import com.dayatang.hrm.organisation.domain.Organization;
import com.dayatang.hrm.staffing.domain.Employee;
import com.dayatang.utils.DateUtils;

/**
 * 薪资帐套
 * 
 * @author chencao
 * 
 */
@Entity
@Table(name = "pay_salary_accountsuite")
@NamedQueries( { @NamedQuery(name = "SalaryAccountSuite.findByOrganization", query = "select new SalaryAccountSuite(o.abolishDate,setting,o.org) from SalaryAccountSuiteSetting setting inner join setting.salaryAccountSuite o inner join o.org org where (org = :pOrg and o.abolishDate >= :pQueryDate and setting.fromDate <= :pQueryDate and setting.toDate >= :pQueryDate)") })
public class SalaryAccountSuite extends AbstractEntity {

	/**
	 * 
	 */
	private static final long serialVersionUID = -8669406545726284027L;

	private static final Logger logger = LoggerFactory
			.getLogger(SalaryAccountSuite.class);

	private static SalaryAccountSuiteRepository salaryAccountSuiteRepository;

	protected static MessageSourceAccessor messages = CompensationMessageSource
			.getAccessor();

	// 废除日期
	@Column(name = "abolish_date")
	@Temporal(TemporalType.TIMESTAMP)
	private Date abolishDate = DateUtils.MAX_DATE;

	// 所属机构
	@ManyToOne
	@JoinColumn(name = "org_id", nullable = false)
	private Organization org;

	/*
	 * =======================================
	 * 
	 * transient
	 * 
	 * ========================================
	 */
	/**
	 * latestSetting为薪资帐套具体设置的一个代理
	 */
	@Transient
	private SalaryAccountSuiteSetting latestSetting;

	public SalaryAccountSuite() {
		if (latestSetting == null) {
			latestSetting = SalaryAccountSuiteSetting.init(this);
		}
	}

	public SalaryAccountSuite(Date abolishDate,
			SalaryAccountSuiteSetting latestSetting, Organization org) {
		this.abolishDate = abolishDate;
		this.org = org;
		this.latestSetting = latestSetting;
	}

	public static SalaryAccountSuiteRepository getSalaryAccountSuiteRepository() {
		if (salaryAccountSuiteRepository == null) {
			salaryAccountSuiteRepository = InstanceFactory
					.getInstance(SalaryAccountSuiteRepository.class);
		}
		return salaryAccountSuiteRepository;
	}

	public static void setSalaryAccountSuiteRepository(
			SalaryAccountSuiteRepository salaryAccountSuiteRepository) {
		SalaryAccountSuite.salaryAccountSuiteRepository = salaryAccountSuiteRepository;
	}

	public static MessageSourceAccessor getMessages() {
		return messages;
	}

	public static void setMessages(MessageSourceAccessor messages) {
		SalaryAccountSuite.messages = messages;
	}

	public Date getAbolishDate() {
		return abolishDate;
	}

	public void setAbolishDate(Date abolishDate) {
		this.abolishDate = abolishDate;
	}

	public Organization getOrg() {
		return org;
	}

	public void setOrg(Organization org) {
		if (isNew()) {
			this.org = org;
		} else if (!org.equals(getOrg())) {
			throw new CompensationRuntimeException(messages.getMessage(
					"SalaryAccountSuite.setOrg.couldNotModify",
					"不能修改薪资帐套所属的机构。"));
		}
	}

	SalaryAccountSuiteSetting getLatestSetting() {
		return latestSetting;
	}

	void setLatestSetting(SalaryAccountSuiteSetting latestSetting) {
		this.latestSetting = latestSetting;
	}

	// 代理属性
	public String getName() {
		return latestSetting.getName();
	}

	public void setName(String name) {
		latestSetting.setName(name);
	}

	public Date getCreated() {
		return latestSetting.getCreated();
	}

	public void setCreated(Date created) {
		latestSetting.setCreated(created);
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof SalaryAccountSuite == false) {
			return false;
		}
		if (this == obj) {
			return true;
		}
		SalaryAccountSuite rhs = (SalaryAccountSuite) obj;
		return new EqualsBuilder().append(getId(), rhs.getId()).isEquals();
	}

	@Override
	public int hashCode() {
		return new HashCodeBuilder(17, 37).append(getId()).toHashCode();
	}

	@Override
	public String toString() {
		return ToStringBuilder.reflectionToString(this,
				ToStringStyle.SHORT_PREFIX_STYLE);
	}

	/*
	 * =======================================
	 * 
	 * action
	 * 
	 * ========================================
	 */

	private void save() {
		Assert.notNull(org, messages.getMessage(
				"SalaryAccountSuite.save.orgIsNull", "薪资帐套所属机构不能为空。"));
		getSalaryAccountSuiteRepository().save(this);
	}

	/**
	 * 改正设置并保存（不会将设置备份）
	 */
	public void saveChanges() {
		save();
		latestSetting.saveChanges();
	}

	/**
	 * 备份原有设置并保存当前设置（会将之前的设置备份）
	 */
	public void archive() {
		save();
		latestSetting.archive();
	}

	public void markArchive() {
		latestSetting.markArchive();
	}

	public void markArchiveAll() {

		markArchive();

		List<SalaryCalculateItem> salaryCalItems = getSalaryCalculateItems(new Date());

		for (SalaryCalculateItem calItem : salaryCalItems) {
			calItem.markArchiveAll();
		}

	}

	void remove() {
		getSalaryAccountSuiteRepository().remove(this);
	}

	/**
	 * 在指定日期撤销薪资帐套
	 * 
	 * @param abolishDate
	 */
	public void abolish(Date abolishDate) {
		if (abolishDate == null) {
			abolishDate = new Date();
		}

		if (logger.isDebugEnabled()) {
			logger.debug("废除薪资帐套【{}】，废除日期【{}】", new Object[] { getName(),
					abolishDate });
		}

		setAbolishDate(abolishDate);
		save();

		latestSetting.abolish(abolishDate);

		// 不必要
		// SalaryAccountSuiteEmployeeAssign.terminate(this);
	}

	/**
	 * 根据指定日期，查找当时薪资帐套下分配的有效员工列表
	 * 
	 * @param queryDate
	 * @return
	 */
	public List<Employee> getEmployees(Date queryDate) {
		return SalaryAccountSuiteEmployeeAssign
				.findEmployeesBySalaryAccountSuite(this, queryDate);
	}

	/**
	 * 根据指定日期，查找当时薪资帐套下有效的薪资项目列表
	 * 
	 * @param queryDate
	 * @return
	 */
	public List<SalaryItem> getSalaryItems(Date queryDate) {
		return SalaryItem.findSalaryItemsBySalaryAccountSuite(this, queryDate);
	}

	/**
	 * 根据指定日期，查找当时薪资帐套下有效的薪资计算项目列表
	 * 
	 * @param queryDate
	 * @return
	 */
	public List<SalaryCalculateItem> getSalaryCalculateItems(Date queryDate) {
		return SalaryCalculateItem
				.findSalaryCalculateItemsBySalaryAccountSuite(this, queryDate);
	}

	public void addSalaryCalculateItem(SalaryCalculateItem item) {
		item.setSalaryAccountSuite(this);
		item.saveChanges();
	}

	public void addSalaryItem(SalaryItem item) {
		item.setSalaryAccountSuite(this);
		item.saveChanges();
	}

	/**
	 * 为薪资帐套指派员工，开始时间：当前，结束时间：无限长
	 * 
	 * @param employee
	 */
	public void assignEmployee(Employee employee) {
		assignEmployee(employee, null, null);
	}

	/**
	 * 在某一时间段，为薪资帐套指派员工
	 * 
	 * @param employee
	 * @param fromDate
	 * @param toDate
	 */
	public void assignEmployee(Employee employee, Date fromDate, Date toDate) {
		if (isNew()) {
			save();
		}

		SalaryAccountSuiteEmployeeAssign.assignEmployeeToSalaryAccountSuite(
				employee, this, fromDate, toDate);

	}

	/**
	 * 为薪资帐套更新所指派的员工集合
	 * 
	 * @param newEmployees
	 */
	public void updateEmployees(Collection<Employee> newEmployees) {
		if (isNew()) {
			save();
		}

		Date now = new Date();

		List<Employee> oldEmployees = getEmployees(now);

		// 添加归属关系
		for (Employee newEmployee : newEmployees) {
			if (oldEmployees.contains(newEmployee)) {
				// 删除原集合中的员工，最终剩下的oldEmployees将是需要被废除的
				oldEmployees.remove(newEmployee);
			} else {
				assignEmployee(newEmployee);
			}
		}

		// 废除归属关系
		for (Employee oldEmployee : oldEmployees) {
			SalaryAccountSuiteEmployeeAssign ascription = SalaryAccountSuiteEmployeeAssign
					.getValid(this, oldEmployee, now);
			if (ascription == null) {
				throw new CompensationRuntimeException(messages.getMessage(
						"SalaryAccountSuite.updateSalaryItems.assignIsNull",
						"薪资帐套下没有该员工的有效归属关系。"));
			} else {
				ascription.terminate(now);
			}
		}
	}

	public boolean containEmployees(Collection<Employee> employees,
			Date queryDate) {

		Assert.notEmpty(employees);
		return getEmployees(queryDate).containsAll(employees);

	}

	/**
	 * 查找指定日期下的薪资帐套（填充薪资帐套设置信息）
	 * 
	 * @param id
	 * @param queryDate
	 * @return
	 */
	public static SalaryAccountSuite get(Long id, Date queryDate) {
		SalaryAccountSuite accountSuite = getSalaryAccountSuiteRepository()
				.get(id);
		if (accountSuite != null) {
			SalaryAccountSuiteSetting accountSuiteSetting = SalaryAccountSuiteSetting
					.get(accountSuite, queryDate);
			if (accountSuiteSetting == null) {
				// accountSuiteSetting = SalaryAccountSuiteSetting
				// .init(accountSuite);
				throw new CompensationRuntimeException(messages.getMessage(
						"SalaryAccountSuite.get.notExist", "该日期下的薪资帐套不存在。"));

			}
			accountSuite.setLatestSetting(accountSuiteSetting);
		} else {
			throw new CompensationRuntimeException(messages.getMessage(
					"SalaryAccountSuite.get.notExist", "该日期下的薪资帐套不存在。"));
		}
		return accountSuite;
	}

	/**
	 * 查找最近的薪资帐套（填充薪资帐套设置信息）
	 * 
	 * @param id
	 * @return
	 */
	public static SalaryAccountSuite getLatest(Long id) {
		return get(id, new Date());
	}

	/**
	 * 根据指定机构和日期，查找机构下直属未被撤销的帐套列表
	 * 
	 * @param org
	 * @param queryDate
	 * @return
	 */
	public static List<SalaryAccountSuite> findByOrganization(Organization org,
			Date queryDate) {
		return getSalaryAccountSuiteRepository().findByOrganization(org,
				queryDate);
	}

}
